package cn.huiyunche.base.service.framework.utils;

import cn.huiyunche.base.service.constant.WeixinConstant;
import cn.huiyunche.base.service.vo.Result;
import cn.huiyunche.base.service.vo.WeixinAccess;
import cn.huiyunche.tools.basic.exceptions.BusinessException;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * 微信工具类
 *
 * @author hdy [Tuffy]
 */
public class WeixinUtils {

    private static final Logger logger = LoggerFactory.getLogger(WeixinUtils.class);
    private static final String weixinType = HYCUtils.getConstantPropertiesLoader().getProperty(WeixinConstant.WEIXIN_TYPE);

    /**
     * 验证服务器认证签名
     *
     * @param signature 签名
     * @param timestamp 时间戳
     * @param nonce     随机串
     * @return 认证状态
     * @throws IOException 异常
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        String[] arr = new String[]{HYCUtils.getConstantPropertiesLoader().getProperty(WeixinConstant.WEIXIN_TOKEN), timestamp, nonce};
        // 将token、timestamp、nonce三个参数进行字典序排序  
        Arrays.sort(arr);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }
        MessageDigest md = null;
        String tmpStr = null;

        try {
            md = MessageDigest.getInstance("SHA-1");
            // 将三个参数字符串拼接成一个字符串进行sha1加密  
            byte[] digest = md.digest(content.toString().getBytes());
            tmpStr = byteToStr(digest);
        } catch (NoSuchAlgorithmException e) {
            WeixinUtils.logger.error("验证微信与服务器权限异常 ：{}", e);
            throw new BusinessException("验证微信与服务器权限异常：" + e.getMessage());
        }

        content = null;
        // 将sha1加密后的字符串可与signature对比，标识该请求来源于微信  
        return tmpStr != null && tmpStr.equals(signature.toUpperCase());
    }

    /**
     * 获取微信access token
     *
     * @return token
     * @throws IOException
     */
    public static String getAccessToken() throws IOException {
        String accessTokenInRedis;
        WeixinAccess wat = WeixinAccess.getInstance();
        // 查看redis缓存. 如果没有再去单例中查找，避免redis服务宕机之后无法获取缓存导致应用无法使用
        if (weixinType.equals(WeixinConstant.WEIXIN_QY_TYPE)) {
            accessTokenInRedis = HYCUtils.getWeixinQyAccessTokenCache();
        } else {
            accessTokenInRedis = HYCUtils.getWeixinAccessTokenCache();
        }
        return resetAccessToken(accessTokenInRedis, weixinType, wat);
    }

    /**
     * 获取Jsapi 配置
     *
     * @return 结果集
     * @throws IOException 异常
     */
    public static Result<Map<String, String>> getJsapiConfig(HttpServletRequest request) throws IOException {
        Result<Map<String, String>> rm = new Result<Map<String, String>>(false, null, null);
        String ticket = getWeixinJsapiTicket();
        if (StringUtils.isBlank(ticket)) {
            rm.setMessage("获取jsapi调用token失败");
            return rm;
        }
        String url = HttpUtils.getHostAddressWithContextPath() + WeixinConstant.WEIXIN_CONTENT_PATH + "?menu="
                + HYCUtils.getWeixinThismenuCache() + "&code=" + HYCUtils.getWeixinUserCodeCache() + "&state=1";
        Map<String, String> map = jsSdkSign(ticket, url);
        map.put("appId", HYCUtils.getConstantPropertiesLoader().getProperty(WeixinConstant.WEIXIN_APP_ID));
        rm.setData(map);
        rm.setSuccess(true);
        return rm;
    }

    /**
     * 获取jsapi调用token
     *
     * @return token
     * @throws IOException
     * @throws MalformedURLException
     */
    public static String getWeixinJsapiTicket() throws IOException {
        WeixinAccess wat = WeixinAccess.getInstance();
        // 企业号
        if (weixinType.equals(WeixinConstant.WEIXIN_QY_TYPE)) {
            if (StringUtils.isBlank(wat.getQyJsapiTicket())
                    || new Date().getTime() - wat.getQyJsapiTimer().getTime() > wat.getExpiresIn() * 1000) {
                JSONObject result = HttpUtils.get(
                        HYCUtils.getConstantPropertiesLoader().getProperty(WeixinConstant.WEIXIN_QY_JSAPI_TICKET_URL)
                                + getAccessToken());
                if (result.containsKey("ticket")) {
                    wat.setQyJsapiTicket(result.getString("ticket"));
                    wat.setExpiresIn(result.getInteger("expires_in"));
                    wat.setQyJsapiTimer(new Date());
                }
            }
            return wat.getQyJsapiTicket();
        }
        // 首次实例化
        if (StringUtils.isBlank(wat.getJsapiTicket())
                || new Date().getTime() - wat.getJsapiTimer().getTime() > wat.getExpiresIn() * 1000) {
            // 获取jsapiTicket
            JSONObject result = HttpUtils
                    .get(HYCUtils.getConstantPropertiesLoader().getProperty(WeixinConstant.WEIXIN_JSAPI_TICKET_URL)
                            + getAccessToken() + "&type=jsapi");
            if (result.containsKey("ticket")) {
                wat.setJsapiTicket(result.getString("ticket"));
                wat.setExpiresIn(result.getInteger("expires_in"));
                wat.setJsapiTimer(new Date());
            }
        }
        return wat.getJsapiTicket();
    }


    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param byteArray
     * @return
     */
    public static String byteToStr(byte[] byteArray) {
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) {
            strDigest += byteToHexStr(byteArray[i]);
        }
        return strDigest;
    }

    /**
     * 将字节转换为十六进制字符串
     *
     * @param mByte
     * @return
     */
    public static String byteToHexStr(byte mByte) {
        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = Digit[mByte & 0X0F];

        String s = new String(tempArr);
        return s;
    }

    public static Map<String, String> jsSdkSign(String jsapi_ticket, String url) {
        Map<String, String> ret = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String string1;
        String signature = "";

        // 注意这里参数名必须全部小写，且必须有序
        string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "&timestamp=" + timestamp + "&url=" + url;
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException e) {
            logger.error("WeixinUtils.jsSdkSign error : {}", e);
            throw new BusinessException("参数错误！");
        } catch (UnsupportedEncodingException e) {
            logger.error("WeixinUtils.jsSdkSign error : {}", e);
            throw new BusinessException("不支持的编码！");
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);

        return ret;
    }

    /**
     * 重新设置token值
     *
     * @param accessTokenInRedis 微信access token
     * @param weixinType         微信公众号类型
     * @param wat                token实例
     * @throws IOException
     * @throws MalformedURLException
     */
    private static String resetAccessToken(String accessTokenInRedis, String weixinType, WeixinAccess wat) throws IOException {
        // 企业号
        if (weixinType.equals(WeixinConstant.WEIXIN_QY_TYPE)) {
            if (new Date().getTime() - wat.getQyAccessTimer().getTime() > wat.getExpiresIn() * 1000) {
                JSONObject result = HttpUtils.get(
                        HYCUtils.getConstantPropertiesLoader().getProperty(WeixinConstant.WEIXIN_QY_ACCESSTOKEN_URL));
                if (result.containsKey("access_token")) {
                    accessTokenInRedis = result.getString("access_token");
                    // 先加入单例，避免redis宕机造成未知影响
                    wat.setQyAccessToken(accessTokenInRedis);
                    wat.setExpiresIn(result.getIntValue("expires_in"));
                    wat.setQyAccessTimer(new Date());
                    // 加入缓存
                    HYCUtils.setWeixinQyAccessTokenCache(accessTokenInRedis);
                }
            }
        } else if (new Date().getTime() - wat.getAccessTimer().getTime() > wat.getExpiresIn() * 1000) {
            JSONObject result = HttpUtils
                    .get(HYCUtils.getConstantPropertiesLoader().getProperty(WeixinConstant.WEIXIN_ACCESSTOKEN_URL));
            if (result.containsKey("access_token")) {
                accessTokenInRedis = result.getString("access_token");
                // 先加入单例，避免redis宕机造成未知影响
                wat.setAccessToken(accessTokenInRedis);
                wat.setExpiresIn(result.getIntValue("expires_in"));
                wat.setAccessTimer(new Date());
                // 加入缓存
                HYCUtils.setWeixinAccessTokenCache(accessTokenInRedis);
            }
        }
        return accessTokenInRedis;
    }

    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    private static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}
